﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Ewk.Runtime.Serialization.PositionBased
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
    public sealed class PositionBasedDatafieldAttribute : DatafieldAttribute
    {
        static readonly Dictionary<Type, DatafieldSerializer<PositionBasedDatafieldAttribute>> _serializers = new Dictionary<Type, DatafieldSerializer<PositionBasedDatafieldAttribute>>();

        static PositionBasedDatafieldAttribute()
        {
            DatafieldSerializer<PositionBasedDatafieldAttribute> datafieldSerializer = null;

            datafieldSerializer = new PositionBasedInt32Serializer();
            _serializers.Add(datafieldSerializer.DatafieldType, datafieldSerializer);

            datafieldSerializer = new PositionBasedDateTimeSerializer();
            _serializers.Add(datafieldSerializer.DatafieldType, datafieldSerializer);
        }

        public PositionBasedDatafieldAttribute(int start, int length)
        {
            if (start < 0) throw new ArgumentException(ExceptionMessages.InvalidStartValue, "start");
            if (length <= 0) throw new ArgumentException(ExceptionMessages.InvalidLengthValue, "length");

            Start = start;
            Length = length;
            DefaultCharacter = default(char);
            Alignment = DatafieldAlignment.Default;
        }

        /// <summary>
        /// Gets the start position of the field within the datarecord.
        /// </summary>
        public int Start { get; private set; }

        /// <summary>
        /// Gets the length of the field.
        /// </summary>
        public int Length { get; private set; }

        /// <summary>
        /// Gets or sets the character that is used for an empty value.
        /// </summary>
        public char DefaultCharacter { get; set; }

        /// <summary>
        /// Gets or sets the alignment of the field.
        /// </summary>
        public DatafieldAlignment Alignment { get; set; }

        public override string Serialize<TDatarecord>(TDatarecord datarecord)
        {
            var value = PropertyInfo.GetValue(datarecord, null);
            var fieldSerializer = GetDatafieldSerializer();

            if (PropertyInfo.PropertyType.IsArray)
            {
                return SerializeArray(value, fieldSerializer);
            }

            return SerializeElement(value, fieldSerializer);
        }

        string SerializeArray(object value, DatafieldSerializer<PositionBasedDatafieldAttribute> fieldSerializer)
        {
            var resultBuilder = new StringBuilder();
            var elements = (Array)value;

            foreach (var element in elements)
            {
                var elementResult = SerializeElement(element, fieldSerializer);
                resultBuilder.Append(elementResult);
           }

            var result = resultBuilder.ToString();

            if (result.Length > Length * Occurrence)
            {
                OnDatafieldOverflow(this, ExceptionMessages.DatafieldOverflow);
            }

            return result;
        }

        string SerializeElement(object value, DatafieldSerializer<PositionBasedDatafieldAttribute> fieldSerializer)
        {
            var result = fieldSerializer.Serialize(value, this);

            if (result.Length > Length)
            {
                OnDatafieldOverflow(this, ExceptionMessages.DatafieldOverflow);
            }

            result = Alignment == DatafieldAlignment.Left
                         ? result.PadRight(Length, DefaultCharacter)
                         : result.PadLeft(Length, DefaultCharacter);

            return result;
        }

        public override object Deserialize(string datarecord)
        {
            var datafield = datarecord.Length < Start + Occurrence * Length ? string.Empty : datarecord.Substring(Start, Occurrence * Length);

            if (string.IsNullOrEmpty(TrimElement(datafield)) && Mandatory)
            {
                OnDatafieldEmpty(this, ExceptionMessages.DatafieldEmpty);
            }

            var fieldSerializer = GetDatafieldSerializer();

            if (PropertyInfo.PropertyType.IsArray)
            {
                return DeserializeArray(datafield, fieldSerializer);
            }

            datafield = TrimElement(datafield);

            return fieldSerializer.Deserialize(datafield, this);
        }

        Array DeserializeArray(string value, DatafieldSerializer<PositionBasedDatafieldAttribute> fieldSerializer)
        {
            var array = Array.CreateInstance(ElementType, Occurrence);

            var position = 0;
            for (var i = 0; i < Occurrence; i++)
            {
                var elementValue = value.Substring(position, Length);
                elementValue = TrimElement(elementValue);

                var result = fieldSerializer.Deserialize(elementValue, this);
                array.SetValue(result, i);

                position += Length;
            }

            return array;
        }

        string TrimElement(string value)
        {
            return Alignment == DatafieldAlignment.Left
                       ? value.TrimEnd(DefaultCharacter)
                       : value.TrimStart(DefaultCharacter);
        }

        DatafieldSerializer<PositionBasedDatafieldAttribute> GetDatafieldSerializer()
        {
            if (SerializerType == null)
            {
                return _serializers[ElementType];
            }

            return (DatafieldSerializer<PositionBasedDatafieldAttribute>)SerializerType.Assembly.CreateInstance(SerializerType.FullName);
        }
    }
}
